using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Content;

namespace Firefly.Framework.ContentManagement
{
    public partial class ContentTracker : ContentManager, IDisposableEx
    {
        private Dictionary<string, AssetTracker> _loadedAssets;
        private List<string> _loadedAssetNames;
        private Stack<AssetTracker> _loadingAssetsStack;
        private bool _unloading;

        public bool IsLoading
        {
            get { return _loadingAssetsStack.Count > 0; }
        }

        public ContentTracker(IServiceProvider serviceProvider)
            : this(serviceProvider, "Content")
        {
        }

        public ContentTracker(IServiceProvider serviceProvider, string rootDirectory)
            : base(serviceProvider, rootDirectory)
        {
            _loadedAssets = new Dictionary<string, AssetTracker>();
            _loadedAssetNames = new List<string>();
            _loadingAssetsStack = new Stack<AssetTracker>();
        }

        public override T Load<T>(string assetName)
        {
            return Load<T>(assetName, null);
        }

        public T Load<T>(string assetName, AssetTracker tracker)
        {
            if (_loadedAssets.ContainsKey(assetName))
            {
                AssetTracker trackerExisting = _loadedAssets[assetName];
                T asset = (T)trackerExisting.Asset;
                trackerExisting.RefCount++;

                if (_loadingAssetsStack.Count > 0)
                {
                    _loadingAssetsStack.Peek().RefersTo.Add(assetName);
                    trackerExisting.ReferredToBy.Add(_loadingAssetsStack.Peek().AssetName);
                }

                return asset;
            }

            if (tracker == null)
            {
                tracker = new AssetTracker();
                tracker.RefCount = 1;
                tracker.AssetName = assetName;
            }

            if (_loadingAssetsStack.Count > 0)
            {
                _loadingAssetsStack.Peek().RefersTo.Add(assetName);
                tracker.ReferredToBy.Add(_loadingAssetsStack.Peek().AssetName);
            }

            _loadingAssetsStack.Push(tracker);

            try
            {
                tracker.Asset = ReadAsset<T>(RootDirectory + "\\" + assetName, tracker.TrackDisposableAsset);
                tracker.Disposables.RemoveAll(delegate(IDisposable d)
                {
                    string tmp = "";
                    return tracker.Asset == d || SearchForAsset(d, out tmp);
                });

            }
            finally
            {
                _loadingAssetsStack.Pop();
            }

            _loadedAssets.Add(assetName, tracker);
            _loadedAssetNames.Add(assetName);
            tracker.Status = AssetStatus.Active;
            return (T)tracker.Asset;
        }

        public override void Unload()
        {
            _unloading = true;
            Dictionary<string, AssetTracker>.Enumerator enumer = _loadedAssets.GetEnumerator();

            while (enumer.MoveNext())
            {
                enumer.Current.Value.NotifyAssetChanged(new AssetChangedEventArgs(null));
                DisposeAssetTracker(enumer.Current.Value, false);
            }

            _loadedAssets.Clear();

            if (_loadThread != null)
            {
                _loadThread.Join();
                _loadThread = null;
            }

            _unloading = false;
        }

        public void Unload(string assetName, bool releaseChildren)
        {
            if (_loadedAssets.ContainsKey(assetName))
            {
                AssetTracker tracker = _loadedAssets[assetName];
                tracker.NotifyAssetChanged(new AssetChangedEventArgs(null));
                DisposeAssetTracker(tracker, releaseChildren);
                _loadedAssets.Remove(assetName);
                _loadedAssetNames.Remove(assetName);
            }
        }

        public void Release(string assetName)
        {
            if (!_unloading)
            {
                if (_loadedAssets.ContainsKey(assetName))
                {
                    AssetTracker tracker = _loadedAssets[assetName];
                    tracker.RefCount--;

                    if (tracker.RefCount == 0)
                    {
                        tracker.NotifyAssetChanged(new AssetChangedEventArgs(null));
                        DisposeAssetTracker(tracker, true);
                        _loadedAssets.Remove(assetName);
                        _loadedAssetNames.Remove(assetName);
                    }
                }
            }
        }

        public T Reload<T>(string assetName)
        {
            if (_loadedAssets.ContainsKey(assetName))
            {
                AssetTracker oldAssetTracker = _loadedAssets[assetName];
                _loadedAssets.Remove(assetName);
                _loadedAssetNames.Remove(assetName);
                T asset = Load<T>(assetName);
                oldAssetTracker.NotifyAssetChanged(new AssetChangedEventArgs(asset));
                DisposeAssetTracker(oldAssetTracker, true);
                return asset;
            }
            else
            {
                return Load<T>(assetName);
            }
        }

        public bool IsLoaded(string assetName)
        {
            if (_loadedAssets.ContainsKey(assetName))
            {
                return true;
            }

            return false;
        }

        public AssetTracker GetTracker(string assetName)
        {
            if (!_loadedAssets.ContainsKey(assetName))
            {
                return null;
            }

            return _loadedAssets[assetName];
        }

        public int GetReferenceCount(string assetName)
        {
            if (!_loadedAssets.ContainsKey(assetName))
            {
                return 0;
            }

            return _loadedAssets[assetName].RefCount;
        }

        public List<string> GetLoadedAssetNames()
        {
            return _loadedAssetNames;
        }

        public bool SearchForAsset(object asset, out string assetName)
        {
            Dictionary<string, AssetTracker>.Enumerator enumer = _loadedAssets.GetEnumerator();

            while (enumer.MoveNext())
            {
                if (asset == enumer.Current.Value.Asset)
                {
                    assetName = enumer.Current.Key;
                    return true;
                }
            }
            assetName = "";
            return false;
        }

        private void DisposeAssetTracker(AssetTracker tracker, bool releaseChildren)
        {
            tracker.NotifyAssetChanged(new AssetChangedEventArgs(null));
            tracker.Status = AssetStatus.Disposed;

            foreach (IDisposable disposable in tracker.Disposables)
            {
                disposable.Dispose();
            }

            if (tracker.Asset is IDisposable)
            {
                ((IDisposable)tracker.Asset).Dispose();
            }

            foreach (string childAsset in tracker.RefersTo)
            {
                if (_loadedAssets.ContainsKey(childAsset))
                {
                    _loadedAssets[childAsset].ReferredToBy.Remove(tracker.AssetName);

                    if (releaseChildren)
                    {
                        Release(childAsset);
                    }
                }
            }
        }

        #region IDisposableEx Members

        public bool Disposed { get; set; }

        #endregion

        #region IDisposable Members

        protected override void Dispose(bool disposing)
        {
            if (!Disposed)
            {
                if (disposing)
                {
                    Unload();
                }

                Disposed = true;
            }
        }

        ~ContentTracker()
        {
            Dispose(false);
        }

        #endregion
    }
}
